package org.metasketch.diagram.javafx.graphics;

import java.util.ArrayList;
import java.util.List;

import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Point2D;
import javafx.geometry.Side;
import javafx.geometry.VPos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;

import org.metasketch.editor.links.LineSection;
import org.metasketch.editor.toolbox.IDraggable;
import org.metasketch.editor.toolbox.ISelectable;
import org.metasketch.graphics.Diagram;
import org.metasketch.graphics.GraphicContainer;
import org.metasketch.graphics.GraphicNode;
import org.metasketch.graphics.Point;
import org.metasketch.javafx.graphics.IJFXGraphicNode;
import org.metasketch.javafx.graphics.JFXDiagram;
import org.metasketch.model.BaseModel;
import org.metasketch.model.CommonIdentified;
import org.metasketch.model.IModelChangeListener;
import org.metasketch.model.ModelChangeManager;

import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.stage.WindowEvent;

public abstract class BaseGraphic extends Group implements IJFXGraphicNode,
		ISelectable, IDraggable {

	protected int x;
	protected int y;
	protected int width;
	protected int height;

	protected Point2D dragAnchor;
	protected double initX;
	protected double initY;

	protected CommonIdentified semantic;
	protected GraphicNode graphicNode;

	// Rectangle selectMask = new Rectangle(1, 1, width, height);

	// Rectangle boundBox = new Rectangle(-5, -5, width, height);
	// protected boolean selected;
	//
	//
	// protected boolean resizable = false;

	protected SelectBox selectBox = new SelectBox(this);
	private ModelChangeManager modelChangeManager;
	private List<DefaultLink> links = new ArrayList<DefaultLink>();

	public BaseGraphic(GraphicNode graphicNode) {
		this.graphicNode = graphicNode;

		// boundBox.setStr

	}

	public boolean isResizing() {
		return selectBox.isMoving();
	}

	public void stopResizing() {
		selectBox.setMoving(false);
	}

	public void setResizable(Resizableness resizableness) {
		selectBox.setResizable(resizableness);
	}

	public CommonIdentified getSemantic() {
		return semantic;
	}

	public GraphicNode getGraphicNode() {
		return graphicNode;
	}

	public void setup(org.metasketch.graphics.BaseGraphic graphic,
			CommonIdentified semantic, Diagram diagram, BaseModel model) {

		graphicNode = (GraphicNode) graphic;
		setup(semantic, diagram, model);
	}

	public void setup(CommonIdentified semantic, Diagram diagram,
			BaseModel model) {
		this.semantic = semantic;

		continueSetup();

		resizeGraphic();
		setSemantic(semantic);
	}

	protected abstract void continueSetup();

	public void setSelected(boolean selected) {
		selectBox.setSelected(selected);

	}

	public void setInitDragParams(double sceneX, double sceneY) {
		initX = getTranslateX();
		initY = getTranslateY();
		dragAnchor = new Point2D(sceneX, sceneY);
	}

	public void move(double sceneX, double sceneY) {

		double dragX = sceneX - dragAnchor.getX();
		double dragY = sceneY - dragAnchor.getY();

		double newXPosition = initX + dragX;
		double newYPosition = initY + dragY;

		setPosition(newXPosition, newYPosition);

	}

	public boolean inside(double areaLeft, double areaTop, double areaWidth,
			double areaHeight) {

		double areaRight = areaLeft + areaWidth;
		double areaBottom = areaTop + areaHeight;

		double nodeRight = x + width;
		double nodeBottom = y + height;

		if (x >= areaLeft && x <= areaRight && y >= areaTop && y <= areaBottom
				&& nodeRight >= areaLeft && nodeRight <= areaRight
				&& nodeBottom >= areaTop && nodeBottom <= areaBottom)
			return true;

		return false;
	}

	public int getX() {
		return x;
	}

	public int getY() {
		return y;
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public void setPosition(double x2, double y2) {

		setX((int) x2);

		setY((int) y2);

		refreshLinks();
	}

	// private void updateGraphicNode() {
	// graphicNode.setX(x);
	// graphicNode.setY(y);
	// graphicNode.setWidth(width);
	// graphicNode.setHeight(height);
	// }

	// setter methods
	public void setX(int x) {
		this.x = x;
		graphicNode.setX(x);

		setTranslateX(x);
	}

	public void setY(int y) {
		this.y = y;
		graphicNode.setY(y);

		setTranslateY(y);
	}

	public void setWidth(int width) {
		this.width = width;
		graphicNode.setWidth(width);

		resizeGraphic();
		refreshLinks();
	}

	public void setHeight(int height) {
		this.height = height;
		graphicNode.setHeight(height);
		resizeGraphic();
		refreshLinks();
	}

	protected abstract void resizeGraphic();

	public void resize(int width, int height) {
		this.width = width;
		graphicNode.setWidth(width);

		this.height = height;
		graphicNode.setHeight(height);

		resizeGraphic();
		refreshLinks();
	}

	@Override
	public void modelChanged(CommonIdentified semantic) {
		setSemantic(semantic);
	}

	private void setSemantic(CommonIdentified semantic) {
		this.semantic = semantic;
		refreshModelGraphic();
	}

	protected abstract void refreshModelGraphic();

	public void setModelChangeManager(ModelChangeManager modelChangeManager) {
		this.modelChangeManager = modelChangeManager;
	}

	public void alertModelChanged() {
		if (modelChangeManager != null)
			modelChangeManager.alertChanges(semantic);

		// refreshLinks();
	}

	public void addModelChangeListener(IModelChangeListener listener) {
		modelChangeManager.registerListener(semantic, listener);
	}

	public boolean isSelected() {
		return selectBox.isSelected();
	}

	public void registerLink(DefaultLink defaultLink) {

		links.add(defaultLink);

	}

	public void unregisterLink(DefaultLink defaultLink) {

		links.remove(defaultLink);

	}

	public Point getCompleteCoordinatesThroughParent() {
		int x, y;

		x = (int) getTranslateX();
		y = (int) getTranslateY();

		Parent parent = getParent();
		if (parent instanceof BaseGraphic) {
			BaseGraphic parentGraphic = (BaseGraphic) parent;
			Point parentPoint = parentGraphic
					.getCompleteCoordinatesThroughParent();
			return new Point(x + parentPoint.getX(), y + parentPoint.getY());
		} else
			return new Point(x, y);
	}

	public Point getCompleteCoordinatesThroughParent(int x, int y) {
		// int x, y;
		//
		// x = (int) getTranslateX();
		// y = (int) getTranslateY();

		Parent parent = getParent();
		if (parent instanceof BaseGraphic) {
			BaseGraphic parentGraphic = (BaseGraphic) parent;
			Point parentPoint = parentGraphic
					.getCompleteCoordinatesThroughParent();
			return new Point(x + parentPoint.getX(), y + parentPoint.getY());
		} else
			return new Point(x, y);
	}

	// TODO this should be in the link itself, not here
	protected void refreshLinks() {
		for (DefaultLink defaultLink : links) {
			defaultLink.refresh(this);
		}

		// also signal children that links need to be updated
		if (graphicNode instanceof GraphicContainer) {
			ObservableList<Node> children = getChildren();
			for (Node node : children) {
				if (node instanceof BaseGraphic) {
					BaseGraphic baseGraphic = (BaseGraphic) node;
					baseGraphic.refreshLinks();
				}
			}
		}

		// refreshModelGraphic();
	}

	@Override
	public void relatedModelChanges() {
		refreshModelGraphic();

	}

	@Override
	public void addedMainNode(CommonIdentified semantic) {
		// TODO Auto-generated method stub

	}

	@Override
	public void removedMainNode(CommonIdentified semantic) {
		// TODO Auto-generated method stub
		// remove from parent, remove all links as well ????
		if (semantic == this.semantic)
			removeFromDiagram();
	}

	public void removeFromDiagram() {
		JFXDiagram diagram = getDiagram();

		// if container, go through children and fix it !!!
		if (graphicNode instanceof GraphicContainer) {
			ObservableList<Node> childNodes = getChildren();
			List<Node> toRemove = new ArrayList<Node>();
			toRemove.addAll(childNodes);
			for (Node node : toRemove) {

				if (node instanceof IJFXGraphicNode) {
					IJFXGraphicNode gnode = (IJFXGraphicNode) node;
					gnode.removeFromDiagram();
				}
			}
		}

		Parent parent = getParent();
		if (parent instanceof BaseGraphic) {

			BaseGraphic bgraph = (BaseGraphic) parent;
			GraphicContainer container = (GraphicContainer) bgraph
					.getGraphicNode();

			// remove from xml
			container.removeNode(graphicNode);

			// parentContainer.addNode(node.getGraphicNode());
			bgraph.getChildren().remove(this);
			diagram.removeNode(this);
		} else {
			diagram.getDiagram().getGraphics().remove(graphicNode);
			diagram.removeNode(this);
		}

		// remove associated links
		List<DefaultLink> toRemove = new ArrayList<DefaultLink>();
		toRemove.addAll(links);
		for (DefaultLink link : toRemove) {
			link.removeFromDiagram();
		}

	}

	public JFXDiagram getDiagram() {
		Parent parent = getParent();
		if (parent instanceof BaseGraphic) {
			BaseGraphic parentGraphic = (BaseGraphic) parent;
			return parentGraphic.getDiagram();
			// return new Point(x + parentPoint.getX(), y + parentPoint.getY());
		} else if (parent instanceof Group)
			return (JFXDiagram) parent.getParent().getParent().getParent()
					.getParent();

		return null;
	}

	public ModelChangeManager getModelChangeManager() {
		return modelChangeManager;
	}

	public List<IJFXGraphicNode> getGraphicNodeClass(Class type) {
		List<IJFXGraphicNode> result = new ArrayList<IJFXGraphicNode>();
		ObservableList<Node> children = getChildren();
		for (Node node : children) {
			if (node instanceof IJFXGraphicNode)
				if (type.isAssignableFrom(node.getClass()))
					result.add((IJFXGraphicNode) node);
		}

		return result;
	}

	public List<IJFXGraphicNode> getGraphicNodeSemanticClass(Class type) {
		List<IJFXGraphicNode> result = new ArrayList<IJFXGraphicNode>();
		ObservableList<Node> children = getChildren();
		for (Node node : children) {
			if (node instanceof IJFXGraphicNode)
				if (type.isAssignableFrom(((IJFXGraphicNode) node)
						.getSemantic().getClass()))
					result.add((IJFXGraphicNode) node);
		}

		return result;
	}

}
